home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / imap-3.0 / non-ANSI / c-client / mh.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-30  |  36.8 KB  |  1,437 lines

  1. /*
  2.  * Program:    MH mail routines
  3.  *
  4.  * Author(s):    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  *        Andrew Cohen
  13.  *        Internet: cohen@bucrf16.bu.edu
  14.  *
  15.  * Date:    23 February 1992
  16.  * Last Edited:    30 June 1993
  17.  *
  18.  * Copyright 1993 by the University of Washington
  19.  *
  20.  *  Permission to use, copy, modify, and distribute this software and its
  21.  * documentation for any purpose and without fee is hereby granted, provided
  22.  * that the above copyright notice appears in all copies and that both the
  23.  * above copyright notice and this permission notice appear in supporting
  24.  * documentation, and that the name of the University of Washington not be
  25.  * used in advertising or publicity pertaining to distribution of the software
  26.  * without specific, written prior permission.  This software is made
  27.  * available "as is", and
  28.  * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
  29.  * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
  30.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
  31.  * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
  32.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  33.  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
  34.  * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
  35.  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  36.  *
  37.  */
  38.  
  39. #include <stdio.h>
  40. #include <ctype.h>
  41. #include <netdb.h>
  42. #include <errno.h>
  43. extern int errno;        /* just in case */
  44. #include <sys/types.h>
  45. #include "mail.h"
  46. #include "osdep.h"
  47. #include <pwd.h>
  48. #include <sys/file.h>
  49. #include <sys/stat.h>
  50. #include <sys/time.h>
  51. #include "mh.h"
  52. #include "rfc822.h"
  53. #include "misc.h"
  54.  
  55. /* MH mail routines */
  56.  
  57.  
  58. /* Driver dispatch used by MAIL */
  59.  
  60. DRIVER mhdriver = {
  61.   "mh",                /* driver name */
  62.   (DRIVER *) NIL,        /* next driver */
  63.   mh_valid,            /* mailbox is valid for us */
  64.   mh_parameters,        /* manipulate parameters */
  65.   mh_find,            /* find mailboxes */
  66.   mh_find_bboards,        /* find bboards */
  67.   mh_find_all,            /* find all mailboxes */
  68.   mh_find_all_bboards,        /* find all bboards */
  69.   mh_subscribe,            /* subscribe to mailbox */
  70.   mh_unsubscribe,        /* unsubscribe from mailbox */
  71.   mh_subscribe_bboard,        /* subscribe to bboard */
  72.   mh_unsubscribe_bboard,    /* unsubscribe from bboard */
  73.   mh_create,            /* create mailbox */
  74.   mh_delete,            /* delete mailbox */
  75.   mh_rename,            /* rename mailbox */
  76.   mh_open,            /* open mailbox */
  77.   mh_close,            /* close mailbox */
  78.   mh_fetchfast,            /* fetch message "fast" attributes */
  79.   mh_fetchflags,        /* fetch message flags */
  80.   mh_fetchstructure,        /* fetch message envelopes */
  81.   mh_fetchheader,        /* fetch message header only */
  82.   mh_fetchtext,            /* fetch message body only */
  83.   mh_fetchbody,            /* fetch message body section */
  84.   mh_setflag,            /* set message flag */
  85.   mh_clearflag,            /* clear message flag */
  86.   mh_search,            /* search for message based on criteria */
  87.   mh_ping,            /* ping mailbox to see if still alive */
  88.   mh_check,            /* check for new messages */
  89.   mh_expunge,            /* expunge deleted messages */
  90.   mh_copy,            /* copy messages to another mailbox */
  91.   mh_move,            /* move messages to another mailbox */
  92.   mh_append,            /* append string message to mailbox */
  93.   mh_gc                /* garbage collect stream */
  94. };
  95.  
  96.                 /* prototype stream */
  97. MAILSTREAM mhproto = {&mhdriver};
  98.  
  99. /* MH mail validate mailbox
  100.  * Accepts: mailbox name
  101.  * Returns: our driver if name is valid, NIL otherwise
  102.  */
  103.  
  104. DRIVER *mh_valid (name)
  105.     char *name;
  106. {
  107.   char tmp[MAILTMPLEN];
  108.   return mh_isvalid (name,tmp) ? &mhdriver : NIL;
  109. }
  110.  
  111.  
  112. /* MH mail test for valid mailbox
  113.  * Accepts: mailbox name
  114.  * Returns: T if valid, NIL otherwise
  115.  */
  116.  
  117. int mh_isvalid (name,tmp)
  118.     char *name;
  119.     char *tmp;
  120. {
  121.   struct stat sbuf;
  122.                                 /* if file, get its status */
  123.   return (*name != '{' && *name != '.' &&
  124.       (stat (mh_file (tmp,name),&sbuf) == 0) &&
  125.       (sbuf.st_mode & S_IFMT) == S_IFDIR);
  126. }
  127.  
  128.  
  129. /* MH mail build file name
  130.  * Accepts: destination string
  131.  *          source
  132.  * Returns: destination
  133.  */
  134.  
  135. char *mh_file (dst,name)
  136.     char *dst;
  137.     char *name;
  138. {
  139. #if 0
  140.   struct passwd *pw;
  141.   char *s,*t,tmp[MAILTMPLEN];
  142.   switch (*name) {
  143.   case '/':            /* absolute file path */
  144.     strcpy (dst,name);        /* copy the mailbox name */
  145.     break;
  146.   case '~':            /* home directory */
  147.     if (name[1] == '/') t = myhomedir ();
  148.     else {
  149.       strcpy (tmp,name + 1);    /* copy user name */
  150.       if (s = strchr (tmp,'/')) *s = '\0';
  151.       t = ((pw = getpwnam (tmp)) && pw->pw_dir) ? pw->pw_dir : "/NOSUCHUSER";
  152.     }
  153.     sprintf (dst,"%s%s",t,(s = strchr (name,'/')) ? s : "");
  154.     break;
  155.   default:            /* relative path, goes in Mail folder */
  156.     sprintf (dst,"%s/Mail/%s",myhomedir (),name);
  157.     break;
  158.   }
  159. #else
  160.   sprintf (dst,"%s/Mail/%s",myhomedir (),name);
  161. #endif
  162.   return dst;
  163. }
  164.  
  165.  
  166. /* MH manipulate driver parameters
  167.  * Accepts: function code
  168.  *        function-dependent value
  169.  * Returns: function-dependent return value
  170.  */
  171.  
  172. void *mh_parameters (function,value)
  173.     long function;
  174.     void *value;
  175. {
  176.   fatal ("Invalid mh_parameters function");
  177.   return NIL;
  178. }
  179.  
  180. /* MH mail find list of mailboxes
  181.  * Accepts: mail stream
  182.  *        pattern to search
  183.  */
  184.  
  185. void mh_find (stream,pat)
  186.     MAILSTREAM *stream;
  187.     char *pat;
  188. {
  189.   void *s = NIL;
  190.   char *t,tmp[MAILTMPLEN];
  191.   while (t = sm_read (&s))    /* read subscription database */
  192.     if ((*t != '{') && strcmp (t,"INBOX") && pmatch (t,pat) &&
  193.     mh_isvalid (t,tmp)) mm_mailbox (t);
  194. }
  195.  
  196.  
  197. /* MH mail find list of bboards
  198.  * Accepts: mail stream
  199.  *        pattern to search
  200.  */
  201.  
  202. void mh_find_bboards (stream,pat)
  203.     MAILSTREAM *stream;
  204.     char *pat;
  205. {
  206.   /* Always a no-op */
  207. }
  208.  
  209. /* MH mail find list of all mailboxes
  210.  * Accepts: mail stream
  211.  *        pattern to search
  212.  */
  213.  
  214. void mh_find_all (stream,pat)
  215.     MAILSTREAM *stream;
  216.     char *pat;
  217. {
  218.   DIR *dirp;
  219.   struct direct *d;
  220.   char tmp[MAILTMPLEN],file[MAILTMPLEN];
  221.   int i = 0;
  222.   char *s,*t;
  223.   if (s = strrchr (pat,'/')) {    /* directory specified in pattern? */
  224.     strncpy (file,pat,i = (++s) - pat);
  225.     file[i] = '\0';        /* tie off prefix */
  226.     t = mh_file (tmp,pat);    /* make fully-qualified file name */
  227.                 /* tie off directory name */
  228.     if (s = strrchr (t,'/')) *s = '\0';
  229.   }
  230.   else t = myhomedir ();    /* use home directory to search */
  231.   if (dirp = opendir (t)) {    /* now open that directory */
  232.     while (d = readdir (dirp)) {/* for each directory entry */
  233.       strcpy (file + i,d->d_name);
  234.       if (pmatch (file,pat) && (mh_isvalid (file,tmp))) mm_mailbox (file);
  235.     }
  236.     closedir (dirp);        /* flush directory */
  237.   }
  238. }
  239.  
  240.  
  241. /* MH mail find list of all bboards
  242.  * Accepts: mail stream
  243.  *        pattern to search
  244.  */
  245.  
  246. void mh_find_all_bboards (stream,pat)
  247.     MAILSTREAM *stream;
  248.     char *pat;
  249. {
  250.   /* Always a no-op */
  251. }
  252.  
  253. /* MH mail subscribe to mailbox
  254.  * Accepts: mail stream
  255.  *        mailbox to add to subscription list
  256.  * Returns: T on success, NIL on failure
  257.  */
  258.  
  259. long mh_subscribe (stream,mailbox)
  260.     MAILSTREAM *stream;
  261.     char *mailbox;
  262. {
  263.   char tmp[MAILTMPLEN];
  264.   return sm_subscribe (mh_file (tmp,mailbox));
  265. }
  266.  
  267.  
  268. /* MH mail unsubscribe to mailbox
  269.  * Accepts: mail stream
  270.  *        mailbox to delete from subscription list
  271.  * Returns: T on success, NIL on failure
  272.  */
  273.  
  274. long mh_unsubscribe (stream,mailbox)
  275.     MAILSTREAM *stream;
  276.     char *mailbox;
  277. {
  278.   char tmp[MAILTMPLEN];
  279.   return sm_unsubscribe (mh_file (tmp,mailbox));
  280. }
  281.  
  282.  
  283. /* MH mail subscribe to bboard
  284.  * Accepts: mail stream
  285.  *        bboard to add to subscription list
  286.  * Returns: T on success, NIL on failure
  287.  */
  288.  
  289. long mh_subscribe_bboard (stream,mailbox)
  290.     MAILSTREAM *stream;
  291.     char *mailbox;
  292. {
  293.   return NIL;            /* never valid for MH */
  294. }
  295.  
  296.  
  297. /* MH mail unsubscribe to bboard
  298.  * Accepts: mail stream
  299.  *        bboard to delete from subscription list
  300.  * Returns: T on success, NIL on failure
  301.  */
  302.  
  303. long mh_unsubscribe_bboard (stream,mailbox)
  304.     MAILSTREAM *stream;
  305.     char *mailbox;
  306. {
  307.   return NIL;            /* never valid for MH */
  308. }
  309.  
  310. /* MH mail create mailbox
  311.  * Accepts: mail stream
  312.  *        mailbox name to create
  313.  * Returns: T on success, NIL on failure
  314.  */
  315.  
  316. long mh_create (stream,mailbox)
  317.     MAILSTREAM *stream;
  318.     char *mailbox;
  319. {
  320.   return NIL;            /* this driver is read-only */
  321. }
  322.  
  323.  
  324. /* MH mail delete mailbox
  325.  *        mailbox name to delete
  326.  * Returns: T on success, NIL on failure
  327.  */
  328.  
  329. long mh_delete (stream,mailbox)
  330.     MAILSTREAM *stream;
  331.     char *mailbox;
  332. {
  333.   return NIL;            /* this driver is read-only */
  334. }
  335.  
  336.  
  337. /* MH mail rename mailbox
  338.  * Accepts: MH mail stream
  339.  *        old mailbox name
  340.  *        new mailbox name
  341.  * Returns: T on success, NIL on failure
  342.  */
  343.  
  344. long mh_rename (stream,old,new)
  345.     MAILSTREAM *stream;
  346.     char *old;
  347.     char *new;
  348. {
  349.   return NIL;            /* this driver is read-only */
  350. }
  351.  
  352. /* MH mail open
  353.  * Accepts: stream to open
  354.  * Returns: stream on success, NIL on failure
  355.  */
  356.  
  357. MAILSTREAM *mh_open (stream)
  358.     MAILSTREAM *stream;
  359. {
  360.   long i,nmsgs;
  361.   long recent = 0;
  362.   char tmp[MAILTMPLEN];
  363.   struct hostent *host_name;
  364.   struct direct **names;
  365.   if (!stream) return &mhproto;    /* return prototype for OP_PROTOTYPE call */
  366.   if (LOCAL) {            /* close old file if stream being recycled */
  367.     mh_close (stream);        /* dump and save the changes */
  368.     stream->dtb = &mhdriver;    /* reattach this driver */
  369.     mail_free_cache (stream);    /* clean up cache */
  370.   }
  371.   mh_file (tmp,stream->mailbox);/* canonicalize the stream mailbox name */
  372.   if (!strcmp (tmp,stream->mailbox)) {
  373.     fs_give ((void **) &stream->mailbox);
  374.     stream->mailbox = cpystr (tmp);
  375.   }
  376.   if (!lhostn) {        /* have local host yet? */
  377.     gethostname(tmp,MAILTMPLEN);/* get local host name */
  378.     lhostn = cpystr ((host_name = gethostbyname (tmp)) ?
  379.              host_name->h_name : tmp);
  380.   }
  381.                 /* scan directory */
  382.   if ((nmsgs = scandir (tmp,&names,mh_select,mh_numsort)) >= 0) {
  383.     stream->local = fs_get (sizeof (MHLOCAL));
  384.     LOCAL->dirty = NIL;        /* no update to .mhrc needed yet */
  385.     LOCAL->dir = cpystr (tmp);    /* copy directory name for later */
  386.                 /* create cache */
  387.     LOCAL->number = (unsigned long *) fs_get (nmsgs * sizeof (unsigned long));
  388.     LOCAL->header = (char **) fs_get (nmsgs * sizeof (char *));
  389.     LOCAL->body = (char **) fs_get (nmsgs * sizeof (char *));
  390.     LOCAL->seen = (char *) fs_get (nmsgs * sizeof (char));
  391.     for (i = 0; i<nmsgs; ++i) {    /* initialize cache */
  392.       LOCAL->number[i] = atoi (names[i]->d_name);
  393.       fs_give ((void **) &names[i]);
  394.       LOCAL->header[i] = LOCAL->body[i] = NIL;
  395.       LOCAL->seen[i] = NIL;
  396.     }
  397.     fs_give ((void **) &names);    /* free directory */
  398.                 /* make temporary buffer */
  399.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = MAXMESSAGESIZE) + 1);
  400.     stream->sequence++;        /* bump sequence number */
  401.     stream->readonly = T;    /* make sure higher level knows readonly */
  402.     mail_exists (stream,nmsgs);    /* notify upper level that messages exist */
  403.  
  404.     while (i < nmsgs) {        /* mark all remaining messages as new */
  405.       LOCAL->seen[i++] = NIL;
  406.       mail_elt (stream,i)->recent = T;
  407.       ++recent;            /* count another recent message */
  408.     }
  409.     mail_recent (stream,recent);/* notify upper level about recent */
  410.                 /* notify if empty bboard */
  411.     if (!(stream->nmsgs || stream->silent)) mm_log ("folder is empty",WARN);
  412.   }
  413.   return LOCAL ? stream : NIL;    /* if stream is alive, return to caller */
  414. }
  415.  
  416.  
  417. /* MH file name selection test
  418.  * Accepts: candidate directory entry
  419.  * Returns: T to use file name, NIL to skip it
  420.  */
  421.  
  422. int mh_select (name)
  423.     struct direct *name;
  424. {
  425.   char c;
  426.   char *s = name->d_name;
  427.   while (c = *s++) if (!isdigit (c)) return NIL;
  428.   return T;
  429. }
  430.  
  431.  
  432. /* MH file name comparision
  433.  * Accepts: first candidate directory entry
  434.  *        second candidate directory entry
  435.  * Returns: negative if d1 < d2, 0 if d1 == d2, postive if d1 > d2
  436.  */
  437.  
  438. int mh_numsort (d1,d2)
  439.     struct direct **d1;
  440.     struct direct **d2;
  441. {
  442.   return (atoi ((*d1)->d_name) - atoi ((*d2)->d_name));
  443. }
  444.  
  445. /* MH mail close
  446.  * Accepts: MAIL stream
  447.  */
  448.  
  449. void mh_close (stream)
  450.     MAILSTREAM *stream;
  451. {
  452.   if (LOCAL) {            /* only if a file is open */
  453.     mh_check (stream);        /* dump final checkpoint */
  454.     if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
  455.     mh_gc (stream,GC_TEXTS);    /* free local cache */
  456.     fs_give ((void **) &LOCAL->number);
  457.     fs_give ((void **) &LOCAL->header);
  458.     fs_give ((void **) &LOCAL->body);
  459.     fs_give ((void **) &LOCAL->seen);
  460.                 /* free local scratch buffer */
  461.     if (LOCAL->buf) fs_give ((void **) &LOCAL->buf);
  462.                 /* nuke the local data */
  463.     fs_give ((void **) &stream->local);
  464.     stream->dtb = NIL;        /* log out the DTB */
  465.   }
  466. }
  467.  
  468. /* MH mail fetch fast information
  469.  * Accepts: MAIL stream
  470.  *        sequence
  471.  */
  472.  
  473. void mh_fetchfast (stream,sequence)
  474.     MAILSTREAM *stream;
  475.     char *sequence;
  476. {
  477.   return;            /* no-op for local mail */
  478. }
  479.  
  480.  
  481. /* MH mail fetch flags
  482.  * Accepts: MAIL stream
  483.  *        sequence
  484.  */
  485.  
  486. void mh_fetchflags (stream,sequence)
  487.     MAILSTREAM *stream;
  488.     char *sequence;
  489. {
  490.   return;            /* no-op for local mail */
  491. }
  492.  
  493.  
  494. /* MH mail fetch message structure
  495.  * Accepts: MAIL stream
  496.  *        message # to fetch
  497.  *        pointer to return body
  498.  * Returns: envelope of this message, body returned in body value
  499.  *
  500.  * Fetches the "fast" information as well
  501.  */
  502.  
  503. ENVELOPE *mh_fetchstructure (stream,msgno,body)
  504.     MAILSTREAM *stream;
  505.     long msgno;
  506.     BODY **body;
  507. {
  508.   char *h,*t;
  509.   LONGCACHE *lelt;
  510.   ENVELOPE **env;
  511.   STRING bs;
  512.   BODY **b;
  513.   if (stream->scache) {        /* short cache */
  514.     if (msgno != stream->msgno){/* flush old poop if a different message */
  515.       mail_free_envelope (&stream->env);
  516.       mail_free_body (&stream->body);
  517.     }
  518.     stream->msgno = msgno;
  519.     env = &stream->env;        /* get pointers to envelope and body */
  520.     b = &stream->body;
  521.   }
  522.   else {            /* long cache */
  523.     lelt = mail_lelt (stream,msgno);
  524.     env = &lelt->env;        /* get pointers to envelope and body */
  525.     b = &lelt->body;
  526.   }
  527.   if ((body && !*b) || !*env) {    /* have the poop we need? */
  528.     mail_free_envelope (env);    /* flush old envelope and body */
  529.     mail_free_body (b);
  530.     h = mh_fetchheader (stream,msgno);
  531.                 /* can't use fetchtext since it'll set seen */
  532.     t = LOCAL->body[msgno - 1] ? LOCAL->body[msgno - 1] : "";
  533.     INIT (&bs,mail_string,(void *) t,strlen (t));
  534.                 /* parse envelope and body */
  535.     rfc822_parse_msg (env,body ? b : NIL,h,strlen (h),&bs,lhostn,LOCAL->buf);
  536.   }
  537.   if (body) *body = *b;        /* return the body */
  538.   return *env;            /* return the envelope */
  539. }
  540.  
  541. /* MH mail fetch message header
  542.  * Accepts: MAIL stream
  543.  *        message # to fetch
  544.  * Returns: message header in RFC822 format
  545.  */
  546.  
  547. char *mh_fetchheader (stream,msgno)
  548.     MAILSTREAM *stream;
  549.     long msgno;
  550. {
  551.   unsigned long i,j;
  552.   int fd;
  553.   char *s,*b,*t;
  554.   long m = msgno - 1;
  555.   long lst = NIL;
  556.   struct stat sbuf;
  557.   struct tm *tm;
  558.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  559.                 /* build message file name */
  560.   sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,LOCAL->number[m]);
  561.   if (!LOCAL->header[m] && ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) {
  562.     fstat (fd,&sbuf);        /* get size of message */
  563.                 /* make plausible IMAPish date string */
  564.     tm = gmtime (&sbuf.st_mtime);
  565.     elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  566.     elt->year = tm->tm_year + 1900 - BASEYEAR;
  567.     elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  568.     elt->seconds = tm->tm_sec;
  569.     elt->zhours = 0; elt->zminutes = 0;
  570.                 /* slurp message */
  571.     read (fd,s = (char *) fs_get (sbuf.st_size +1),sbuf.st_size);
  572.     s[sbuf.st_size] = '\0';    /* tie off file */
  573.     close (fd);            /* flush message file */
  574.                 /* find end of header and count lines */
  575.     for (i=1,b=s; *b && !(lst && (*b == '\n'));) if (lst = (*b++ == '\n')) i++;
  576.                 /* copy header in CRLF form */
  577.     LOCAL->header[m] = (char *) fs_get (i += (j = b - s));
  578.     elt->rfc822_size = i - 1;    /* size of message header */
  579.     strcrlfcpy (&LOCAL->header[m],&i,s,j);
  580.                 /* copy body in CRLF form */
  581.     for (i = 1,t = b; *t;) if (*t++ == '\n') i++;
  582.     LOCAL->body[m] = (char *) fs_get (i += (j = t - b));
  583.     elt->rfc822_size += i - 1;    /* size of entire message */
  584.     strcrlfcpy (&LOCAL->body[m],&i,b,j);
  585.     fs_give ((void **) &s);    /* flush old data */
  586.   }
  587.   return LOCAL->header[m] ? LOCAL->header[m] : "";
  588. }
  589.  
  590. /* MH mail fetch message text (only)
  591.     body only;
  592.  * Accepts: MAIL stream
  593.  *        message # to fetch
  594.  * Returns: message text in RFC822 format
  595.  */
  596.  
  597. char *mh_fetchtext (stream,msgno)
  598.     MAILSTREAM *stream;
  599.     long msgno;
  600. {
  601.   long i = msgno - 1;
  602.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  603.                 /* snarf message in case don't have it yet */
  604.   mh_fetchheader (stream,msgno);
  605.   if (!elt->seen) {        /* if message not seen before */
  606.     elt->seen = T;        /* mark as seen */
  607.     LOCAL->dirty = T;        /* and that stream is now dirty */
  608.   }
  609.   LOCAL->seen[i] = T;
  610.   return LOCAL->body[i] ? LOCAL->body[i] : "";
  611. }
  612.  
  613. /* MH fetch message body as a structure
  614.  * Accepts: Mail stream
  615.  *        message # to fetch
  616.  *        section specifier
  617.  *        pointer to length
  618.  * Returns: pointer to section of message body
  619.  */
  620.  
  621. char *mh_fetchbody (stream,m,s,len)
  622.     MAILSTREAM *stream;
  623.     long m;
  624.     char *s;
  625.     unsigned long *len;
  626. {
  627.   BODY *b;
  628.   PART *pt;
  629.   unsigned long i;
  630.   char *base;
  631.   unsigned long offset = 0;
  632.   MESSAGECACHE *elt = mail_elt (stream,m);
  633.                 /* make sure have a body */
  634.   if (!(mh_fetchstructure (stream,m,&b) && b && s && *s &&
  635.     ((i = strtol (s,&s,10)) > 0) && (base = mh_fetchtext (stream,m))))
  636.     return NIL;
  637.   do {                /* until find desired body part */
  638.                 /* multipart content? */
  639.     if (b->type == TYPEMULTIPART) {
  640.       pt = b->contents.part;    /* yes, find desired part */
  641.       while (--i && (pt = pt->next));
  642.       if (!pt) return NIL;    /* bad specifier */
  643.                 /* note new body, check valid nesting */
  644.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  645.       offset = pt->offset;    /* get new offset */
  646.     }
  647.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  648.                 /* need to go down further? */
  649.     if (i = *s) switch (b->type) {
  650.     case TYPEMESSAGE:        /* embedded message */
  651.       offset = b->contents.msg.offset;
  652.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  653.     case TYPEMULTIPART:        /* multipart, get next section */
  654.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  655.     default:            /* bogus subpart specification */
  656.       return NIL;
  657.     }
  658.   } while (i);
  659.                 /* lose if body bogus */
  660.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  661.   if (!elt->seen) {        /* if message not seen before */
  662.     elt->seen = T;        /* mark as seen */
  663.     LOCAL->dirty = T;        /* and that stream is now dirty */
  664.   }
  665.   LOCAL->seen[m-1] = T;
  666.   return rfc822_contents (&LOCAL->buf,&LOCAL->buflen,len,base + offset,
  667.               b->size.ibytes,b->encoding);
  668. }
  669.  
  670. /* MH mail set flag
  671.  * Accepts: MAIL stream
  672.  *        sequence
  673.  *        flag(s)
  674.  */
  675.  
  676. void mh_setflag (stream,sequence,flag)
  677.     MAILSTREAM *stream;
  678.     char *sequence;
  679.     char *flag;
  680. {
  681.   MESSAGECACHE *elt;
  682.   long i;
  683.   short f = mh_getflags (stream,flag);
  684.   short f1 = f & (fSEEN|fDELETED);
  685.   if (!f) return;        /* no-op if no flags to modify */
  686.                 /* get sequence and loop on it */
  687.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  688.     if ((elt = mail_elt (stream,i))->sequence) {
  689.                 /* set all requested flags */
  690.       if (f&fSEEN) elt->seen = T;
  691.       if (f&fDELETED) elt->deleted = T;
  692.       if (f&fFLAGGED) elt->flagged = T;
  693.       if (f&fANSWERED) elt->answered = T;
  694.       if (f1 && !LOCAL->seen[i - 1]) LOCAL->seen[i - 1] = LOCAL->dirty = T;
  695.     }
  696. }
  697.  
  698.  
  699. /* MH mail clear flag
  700.  * Accepts: MAIL stream
  701.  *        sequence
  702.  *        flag(s)
  703.  */
  704.  
  705. void mh_clearflag (stream,sequence,flag)
  706.     MAILSTREAM *stream;
  707.     char *sequence;
  708.     char *flag;
  709. {
  710.   MESSAGECACHE *elt;
  711.   long i = stream->nmsgs;
  712.   short f = mh_getflags (stream,flag);
  713.   short f1 = f & (fSEEN|fDELETED);
  714.   if (!f) return;        /* no-op if no flags to modify */
  715.                 /* get sequence and loop on it */
  716.   if (mail_sequence (stream,sequence)) for (i = 1; i <= stream->nmsgs; i++)
  717.     if ((elt = mail_elt (stream,i))->sequence) {
  718.                 /* clear all requested flags */
  719.       if (f&fSEEN) elt->seen = NIL;
  720.       if (f&fDELETED) elt->deleted = NIL;
  721.       if (f&fFLAGGED) elt->flagged = NIL;
  722.       if (f&fANSWERED) elt->answered = NIL;
  723.                 /* clearing either seen or deleted does this */
  724.       if (f1 && LOCAL->seen[i - 1]) {
  725.     LOCAL->seen[i - 1] = NIL;
  726.     LOCAL->dirty = T;    /* mark stream as dirty */
  727.       }
  728.     }
  729. }
  730.  
  731. /* MH mail search for messages
  732.  * Accepts: MAIL stream
  733.  *        search criteria
  734.  */
  735.  
  736. void mh_search (stream,criteria)
  737.     MAILSTREAM *stream;
  738.     char *criteria;
  739. {
  740.   long i,n;
  741.   char *d;
  742.   search_t f;
  743.                 /* initially all searched */
  744.   for (i = 1; i <= stream->nmsgs; ++i) mail_elt (stream,i)->searched = T;
  745.                 /* get first criterion */
  746.   if (criteria && (criteria = strtok (criteria," "))) {
  747.                 /* for each criterion */
  748.     for (; criteria; (criteria = strtok (NIL," "))) {
  749.       f = NIL; d = NIL; n = 0;    /* init then scan the criterion */
  750.       switch (*ucase (criteria)) {
  751.       case 'A':            /* possible ALL, ANSWERED */
  752.     if (!strcmp (criteria+1,"LL")) f = mh_search_all;
  753.     else if (!strcmp (criteria+1,"NSWERED")) f = mh_search_answered;
  754.     break;
  755.       case 'B':            /* possible BCC, BEFORE, BODY */
  756.     if (!strcmp (criteria+1,"CC"))
  757.       f = mh_search_string (mh_search_bcc,&d,&n);
  758.     else if (!strcmp (criteria+1,"EFORE"))
  759.       f = mh_search_date (mh_search_before,&n);
  760.     else if (!strcmp (criteria+1,"ODY"))
  761.       f = mh_search_string (mh_search_body,&d,&n);
  762.     break;
  763.       case 'C':            /* possible CC */
  764.     if (!strcmp (criteria+1,"C"))
  765.       f = mh_search_string (mh_search_cc,&d,&n);
  766.     break;
  767.       case 'D':            /* possible DELETED */
  768.     if (!strcmp (criteria+1,"ELETED")) f = mh_search_deleted;
  769.     break;
  770.       case 'F':            /* possible FLAGGED, FROM */
  771.     if (!strcmp (criteria+1,"LAGGED")) f = mh_search_flagged;
  772.     else if (!strcmp (criteria+1,"ROM"))
  773.       f = mh_search_string (mh_search_from,&d,&n);
  774.     break;
  775.       case 'K':            /* possible KEYWORD */
  776.     if (!strcmp (criteria+1,"EYWORD"))
  777.       f = mh_search_flag (mh_search_keyword,&d);
  778.     break;
  779.       case 'N':            /* possible NEW */
  780.     if (!strcmp (criteria+1,"EW")) f = mh_search_new;
  781.     break;
  782.  
  783.       case 'O':            /* possible OLD, ON */
  784.     if (!strcmp (criteria+1,"LD")) f = mh_search_old;
  785.     else if (!strcmp (criteria+1,"N"))
  786.       f = mh_search_date (mh_search_on,&n);
  787.     break;
  788.       case 'R':            /* possible RECENT */
  789.     if (!strcmp (criteria+1,"ECENT")) f = mh_search_recent;
  790.     break;
  791.       case 'S':            /* possible SEEN, SINCE, SUBJECT */
  792.     if (!strcmp (criteria+1,"EEN")) f = mh_search_seen;
  793.     else if (!strcmp (criteria+1,"INCE"))
  794.       f = mh_search_date (mh_search_since,&n);
  795.     else if (!strcmp (criteria+1,"UBJECT"))
  796.       f = mh_search_string (mh_search_subject,&d,&n);
  797.     break;
  798.       case 'T':            /* possible TEXT, TO */
  799.     if (!strcmp (criteria+1,"EXT"))
  800.       f = mh_search_string (mh_search_text,&d,&n);
  801.     else if (!strcmp (criteria+1,"O"))
  802.       f = mh_search_string (mh_search_to,&d,&n);
  803.     break;
  804.       case 'U':            /* possible UN* */
  805.     if (criteria[1] == 'N') {
  806.       if (!strcmp (criteria+2,"ANSWERED")) f = mh_search_unanswered;
  807.       else if (!strcmp (criteria+2,"DELETED")) f = mh_search_undeleted;
  808.       else if (!strcmp (criteria+2,"FLAGGED")) f = mh_search_unflagged;
  809.       else if (!strcmp (criteria+2,"KEYWORD"))
  810.         f = mh_search_flag (mh_search_unkeyword,&d);
  811.       else if (!strcmp (criteria+2,"SEEN")) f = mh_search_unseen;
  812.     }
  813.     break;
  814.       default:            /* we will barf below */
  815.     break;
  816.       }
  817.       if (!f) {            /* if can't determine any criteria */
  818.     sprintf (LOCAL->buf,"Unknown search criterion: %s",criteria);
  819.     mm_log (LOCAL->buf,ERROR);
  820.     return;
  821.       }
  822.                 /* run the search criterion */
  823.       for (i = 1; i <= stream->nmsgs; ++i)
  824.     if (mail_elt (stream,i)->searched && !(*f) (stream,i,d,n))
  825.       mail_elt (stream,i)->searched = NIL;
  826.     }
  827.                 /* report search results to main program */
  828.     for (i = 1; i <= stream->nmsgs; ++i)
  829.       if (mail_elt (stream,i)->searched) mail_searched (stream,i);
  830.   }
  831. }
  832.  
  833. /* MH mail ping mailbox
  834.  * Accepts: MAIL stream
  835.  * Returns: T if stream alive, else NIL
  836.  */
  837.  
  838. long mh_ping (stream)
  839.     MAILSTREAM *stream;
  840. {
  841.   return T;            /* always alive */
  842. }
  843.  
  844.  
  845. /* MH mail check mailbox
  846.  * Accepts: MAIL stream
  847.  */
  848.  
  849. void mh_check (stream)
  850.     MAILSTREAM *stream;
  851. {
  852.  /* A no-op for starters */
  853. }
  854.  
  855.  
  856. /* MH mail expunge mailbox
  857.  * Accepts: MAIL stream
  858.  */
  859.  
  860. void mh_expunge (stream)
  861.     MAILSTREAM *stream;
  862. {
  863.   if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
  864. }
  865.  
  866. /* MH mail copy message(s)
  867.     s;
  868.  * Accepts: MAIL stream
  869.  *        sequence
  870.  *        destination mailbox
  871.  * Returns: T if copy successful, else NIL
  872.  */
  873.  
  874. long mh_copy (stream,sequence,mailbox)
  875.     MAILSTREAM *stream;
  876.     char *sequence;
  877.     char *mailbox;
  878. {
  879.   char tmp[MAILTMPLEN];
  880.   char lock[MAILTMPLEN];
  881.   struct iovec iov[3];
  882.   struct stat ssbuf,dsbuf;
  883.   char *t,*v;
  884.   int sfd,dfd;
  885.   long i;
  886.   long r = NIL;
  887.                 /* get sequence to do */
  888.   if (!mail_sequence (stream,sequence)) return NIL;
  889.                 /* get destination mailbox */
  890.   if ((dfd = bezerk_lock (bezerk_file (tmp,mailbox),O_WRONLY|O_APPEND|O_CREAT,
  891.               S_IREAD|S_IWRITE,lock,LOCK_EX)) < 0) {
  892.     sprintf (LOCAL->buf,"Can't open destination mailbox: %s",strerror (errno));
  893.     mm_log (LOCAL->buf,ERROR);
  894.     return NIL;
  895.   }
  896.   mm_critical (stream);        /* go critical */
  897.   fstat (dfd,&dsbuf);        /* get current file size */
  898.   iov[2].iov_base = "\n\n";    /* constant trailer */
  899.   iov[2].iov_len = 2;
  900.  
  901.                 /* write all requested messages to mailbox */
  902.   for (i = 1; i <= stream->nmsgs; i++) if (mail_elt (stream,i)->sequence) {
  903.                 /* build message file name */
  904.     sprintf (tmp,"%s/%lu",LOCAL->dir,LOCAL->number[i - 1]);
  905.     if ((sfd = open (tmp,O_RDONLY,NIL)) >= 0) {
  906.       fstat (sfd,&ssbuf);    /* get size of message */
  907.                 /* ensure enough room */
  908.       if (ssbuf.st_size > LOCAL->buflen) {
  909.                 /* fs_resize does an unnecessary copy */
  910.     fs_give ((void **) &LOCAL->buf);
  911.     LOCAL->buf = (char *) fs_get ((LOCAL->buflen = ssbuf.st_size) + 1);
  912.       }
  913.                 /* slurp the silly thing in */
  914.       read (sfd,iov[1].iov_base = LOCAL->buf,iov[1].iov_len = ssbuf.st_size);
  915.                 /* tie off file */
  916.       iov[1].iov_base[ssbuf.st_size] = '\0';
  917.       close (sfd);        /* flush message file */
  918.                 /* get Path: data */
  919.       if ((((t = iov[1].iov_base - 1) && t[1] == 'P' && t[2] == 'a' &&
  920.         t[3] == 't' && t[4] == 'h' && t[5] == ':' && t[6] == ' ') ||
  921.        (t = strstr (iov[1].iov_base,"\nPath: "))) &&
  922.       (v = strchr (t += 7,'\n')) && (r = v - t)) {
  923.     strcpy (tmp,"From ");    /* start text */
  924.     strncpy (v = tmp+5,t,r);/* copy that many characters */
  925.     v[r++] = ' ';        /* delimiter */
  926.     v[r] = '\0';        /* tie it off */
  927.       }
  928.       else strcpy (tmp,"From somebody ");
  929.                 /* add the time and a newline */
  930.       strcat (tmp,ctime (&ssbuf.st_mtime));
  931.       iov[0].iov_len = strlen (iov[0].iov_base = tmp);
  932.                 /* now do the write */
  933.       if (r = (writev (dfd,iov,3) < 0)) {
  934.     sprintf (LOCAL->buf,"Message copy %d failed: %s",i,strerror (errno));
  935.     mm_log (LOCAL->buf,ERROR);
  936.     ftruncate (dfd,dsbuf.st_size);
  937.     break;            /* give up */
  938.       }
  939.     }
  940.   }
  941.   fsync (dfd);            /* force out the update */
  942.   bezerk_unlock (dfd,NIL,lock);    /* unlock and close mailbox */
  943.   mm_nocritical (stream);    /* release critical */
  944.   return !r;            /* return whether or not succeeded */
  945. }
  946.  
  947. /* MH mail move message(s)
  948.     s;
  949.  * Accepts: MAIL stream
  950.  *        sequence
  951.  *        destination mailbox
  952.  * Returns: T if move successful, else NIL
  953.  */
  954.  
  955. long mh_move (stream,sequence,mailbox)
  956.     MAILSTREAM *stream;
  957.     char *sequence;
  958.     char *mailbox;
  959. {
  960.   long i;
  961.   MESSAGECACHE *elt;
  962.   if (!(mail_sequence (stream,sequence) &&
  963.     mh_copy (stream,sequence,mailbox))) return NIL;
  964.                 /* delete all requested messages */
  965.   for (i = 1; i <= stream->nmsgs; i++)
  966.     if ((elt = mail_elt (stream,i))->sequence) {
  967.       elt->deleted = T;        /* mark message deleted */
  968.       LOCAL->dirty = T;        /* mark mailbox as dirty */
  969.       LOCAL->seen[i - 1] = T;    /* and seen for .mhrc update */
  970.     }
  971.   return T;
  972. }
  973.  
  974.  
  975. /* MH mail append message from stringstruct
  976.  * Accepts: MAIL stream
  977.  *        destination mailbox
  978.  *        stringstruct of messages to append
  979.  * Returns: T if append successful, else NIL
  980.  */
  981.  
  982. long mh_append (stream,mailbox,message)
  983.     MAILSTREAM *stream;
  984.     char *mailbox;
  985.     STRING *message;
  986. {
  987.   mm_log ("Append not valid for read-only mh mailbox",ERROR);
  988.   return NIL;
  989. }
  990.  
  991.  
  992. /* MH garbage collect stream
  993.  * Accepts: Mail stream
  994.  *        garbage collection flags
  995.  */
  996.  
  997. void mh_gc (stream,gcflags)
  998.     MAILSTREAM *stream;
  999.     long gcflags;
  1000. {
  1001.   unsigned long i;
  1002.   if (gcflags & GC_TEXTS)    /* garbage collect texts? */
  1003.                 /* flush texts from cache */
  1004.     for (i = 0; i < stream->nmsgs; i++) {
  1005.       if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  1006.       if (LOCAL->body[i]) fs_give ((void **) &LOCAL->body[i]);
  1007.     }
  1008. }
  1009.  
  1010. /* Internal routines */
  1011.  
  1012.  
  1013. /* Parse flag list
  1014.  * Accepts: MAIL stream
  1015.  *        flag list as a character string
  1016.  * Returns: flag command list
  1017.  */
  1018.  
  1019. short mh_getflags (stream,flag)
  1020.     MAILSTREAM *stream;
  1021.     char *flag;
  1022. {
  1023.   char *t;
  1024.   short f = 0;
  1025.   short i,j;
  1026.   if (flag && *flag) {        /* no-op if no flag string */
  1027.                 /* check if a list and make sure valid */
  1028.     if ((i = (*flag == '(')) ^ (flag[strlen (flag)-1] == ')')) {
  1029.       mm_log ("Bad flag list",ERROR);
  1030.       return NIL;
  1031.     }
  1032.                 /* copy the flag string w/o list construct */
  1033.     strncpy (LOCAL->buf,flag+i,(j = strlen (flag) - (2*i)));
  1034.     LOCAL->buf[j] = '\0';
  1035.     t = ucase (LOCAL->buf);    /* uppercase only from now on */
  1036.  
  1037.     while (*t) {        /* parse the flags */
  1038.       if (*t == '\\') {        /* system flag? */
  1039.     switch (*++t) {        /* dispatch based on first character */
  1040.     case 'S':        /* possible \Seen flag */
  1041.       if (t[1] == 'E' && t[2] == 'E' && t[3] == 'N') i = fSEEN;
  1042.       t += 4;        /* skip past flag name */
  1043.       break;
  1044.     case 'D':        /* possible \Deleted flag */
  1045.       if (t[1] == 'E' && t[2] == 'L' && t[3] == 'E' && t[4] == 'T' &&
  1046.           t[5] == 'E' && t[6] == 'D') i = fDELETED;
  1047.       t += 7;        /* skip past flag name */
  1048.       break;
  1049.     case 'F':        /* possible \Flagged flag */
  1050.       if (t[1] == 'L' && t[2] == 'A' && t[3] == 'G' && t[4] == 'G' &&
  1051.           t[5] == 'E' && t[6] == 'D') i = fFLAGGED;
  1052.       t += 7;        /* skip past flag name */
  1053.       break;
  1054.     case 'A':        /* possible \Answered flag */
  1055.       if (t[1] == 'N' && t[2] == 'S' && t[3] == 'W' && t[4] == 'E' &&
  1056.           t[5] == 'R' && t[6] == 'E' && t[7] == 'D') i = fANSWERED;
  1057.       t += 8;        /* skip past flag name */
  1058.       break;
  1059.     default:        /* unknown */
  1060.       i = 0;
  1061.       break;
  1062.     }
  1063.                 /* add flag to flags list */
  1064.     if (i && ((*t == '\0') || (*t++ == ' '))) f |= i;
  1065.     else {            /* bitch about bogus flag */
  1066.       mm_log ("Unknown system flag",ERROR);
  1067.       return NIL;
  1068.     }
  1069.       }
  1070.       else {            /* no user flags yet */
  1071.     mm_log ("Unknown flag",ERROR);
  1072.     return NIL;
  1073.       }
  1074.     }
  1075.   }
  1076.   return f;
  1077. }
  1078.  
  1079. /* Search support routines
  1080.  * Accepts: MAIL stream
  1081.  *        message number
  1082.  *        pointer to additional data
  1083.  *        pointer to temporary buffer
  1084.  * Returns: T if search matches, else NIL
  1085.  */
  1086.  
  1087. char mh_search_all (stream,msgno,d,n)
  1088.     MAILSTREAM *stream;
  1089.     long msgno;
  1090.     char *d;
  1091.     long n;
  1092. {
  1093.   return T;            /* ALL always succeeds */
  1094. }
  1095.  
  1096.  
  1097. char mh_search_answered (stream,msgno,d,n)
  1098.     MAILSTREAM *stream;
  1099.     long msgno;
  1100.     char *d;
  1101.     long n;
  1102. {
  1103.   return mail_elt (stream,msgno)->answered ? T : NIL;
  1104. }
  1105.  
  1106.  
  1107. char mh_search_deleted (stream,msgno,d,n)
  1108.     MAILSTREAM *stream;
  1109.     long msgno;
  1110.     char *d;
  1111.     long n;
  1112. {
  1113.   return mail_elt (stream,msgno)->deleted ? T : NIL;
  1114. }
  1115.  
  1116.  
  1117. char mh_search_flagged (stream,msgno,d,n)
  1118.     MAILSTREAM *stream;
  1119.     long msgno;
  1120.     char *d;
  1121.     long n;
  1122. {
  1123.   return mail_elt (stream,msgno)->flagged ? T : NIL;
  1124. }
  1125.  
  1126.  
  1127. char mh_search_keyword (stream,msgno,d,n)
  1128.     MAILSTREAM *stream;
  1129.     long msgno;
  1130.     char *d;
  1131.     long n;
  1132. {
  1133.   return NIL;            /* keywords not supported yet */
  1134. }
  1135.  
  1136.  
  1137. char mh_search_new (stream,msgno,d,n)
  1138.     MAILSTREAM *stream;
  1139.     long msgno;
  1140.     char *d;
  1141.     long n;
  1142. {
  1143.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1144.   return (elt->recent && !elt->seen) ? T : NIL;
  1145. }
  1146.  
  1147. char mh_search_old (stream,msgno,d,n)
  1148.     MAILSTREAM *stream;
  1149.     long msgno;
  1150.     char *d;
  1151.     long n;
  1152. {
  1153.   return mail_elt (stream,msgno)->recent ? NIL : T;
  1154. }
  1155.  
  1156.  
  1157. char mh_search_recent (stream,msgno,d,n)
  1158.     MAILSTREAM *stream;
  1159.     long msgno;
  1160.     char *d;
  1161.     long n;
  1162. {
  1163.   return mail_elt (stream,msgno)->recent ? T : NIL;
  1164. }
  1165.  
  1166.  
  1167. char mh_search_seen (stream,msgno,d,n)
  1168.     MAILSTREAM *stream;
  1169.     long msgno;
  1170.     char *d;
  1171.     long n;
  1172. {
  1173.   return mail_elt (stream,msgno)->seen ? T : NIL;
  1174. }
  1175.  
  1176.  
  1177. char mh_search_unanswered (stream,msgno,d,n)
  1178.     MAILSTREAM *stream;
  1179.     long msgno;
  1180.     char *d;
  1181.     long n;
  1182. {
  1183.   return mail_elt (stream,msgno)->answered ? NIL : T;
  1184. }
  1185.  
  1186.  
  1187. char mh_search_undeleted (stream,msgno,d,n)
  1188.     MAILSTREAM *stream;
  1189.     long msgno;
  1190.     char *d;
  1191.     long n;
  1192. {
  1193.   return mail_elt (stream,msgno)->deleted ? NIL : T;
  1194. }
  1195.  
  1196.  
  1197. char mh_search_unflagged (stream,msgno,d,n)
  1198.     MAILSTREAM *stream;
  1199.     long msgno;
  1200.     char *d;
  1201.     long n;
  1202. {
  1203.   return mail_elt (stream,msgno)->flagged ? NIL : T;
  1204. }
  1205.  
  1206.  
  1207. char mh_search_unkeyword (stream,msgno,d,n)
  1208.     MAILSTREAM *stream;
  1209.     long msgno;
  1210.     char *d;
  1211.     long n;
  1212. {
  1213.   return T;            /* keywords not supported yet */
  1214. }
  1215.  
  1216.  
  1217. char mh_search_unseen (stream,msgno,d,n)
  1218.     MAILSTREAM *stream;
  1219.     long msgno;
  1220.     char *d;
  1221.     long n;
  1222. {
  1223.   return mail_elt (stream,msgno)->seen ? NIL : T;
  1224. }
  1225.  
  1226. char mh_search_before (stream,msgno,d,n)
  1227.     MAILSTREAM *stream;
  1228.     long msgno;
  1229.     char *d;
  1230.     long n;
  1231. {
  1232.   return (char) (mh_msgdate (stream,msgno) < n);
  1233. }
  1234.  
  1235.  
  1236. char mh_search_on (stream,msgno,d,n)
  1237.     MAILSTREAM *stream;
  1238.     long msgno;
  1239.     char *d;
  1240.     long n;
  1241. {
  1242.   return (char) (mh_msgdate (stream,msgno) == n);
  1243. }
  1244.  
  1245.  
  1246. char mh_search_since (stream,msgno,d,n)
  1247.     MAILSTREAM *stream;
  1248.     long msgno;
  1249.     char *d;
  1250.     long n;
  1251. {
  1252.                 /* everybody interprets "since" as .GE. */
  1253.   return (char) (mh_msgdate (stream,msgno) >= n);
  1254. }
  1255.  
  1256.  
  1257. unsigned long mh_msgdate (stream,msgno)
  1258.     MAILSTREAM *stream;
  1259.     long msgno;
  1260. {
  1261.   struct stat sbuf;
  1262.   struct tm *tm;
  1263.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1264.   if (!elt->day) {        /* get date if don't have it yet */
  1265.                 /* build message file name */
  1266.     sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,LOCAL->number[msgno - 1]);
  1267.     stat (LOCAL->buf,&sbuf);    /* get message date */
  1268.     tm = gmtime (&sbuf.st_mtime);
  1269.     elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
  1270.     elt->year = tm->tm_year + 1900 - BASEYEAR;
  1271.     elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
  1272.     elt->seconds = tm->tm_sec;
  1273.     elt->zhours = 0; elt->zminutes = 0;
  1274.   }
  1275.   return (long) (elt->year << 9) + (elt->month << 5) + elt->day;
  1276. }
  1277.  
  1278. char mh_search_body (stream,msgno,d,n)
  1279.     MAILSTREAM *stream;
  1280.     long msgno;
  1281.     char *d;
  1282.     long n;
  1283. {
  1284.   long i = msgno - 1;
  1285.   mh_fetchheader (stream,msgno);
  1286.   return LOCAL->body[i] ?
  1287.     search (LOCAL->body[i],strlen (LOCAL->body[i]),d,n) : NIL;
  1288. }
  1289.  
  1290.  
  1291. char mh_search_subject (stream,msgno,d,n)
  1292.     MAILSTREAM *stream;
  1293.     long msgno;
  1294.     char *d;
  1295.     long n;
  1296. {
  1297.   char *t = mh_fetchstructure (stream,msgno,NIL)->subject;
  1298.   return t ? search (t,strlen (t),d,n) : NIL;
  1299. }
  1300.  
  1301.  
  1302. char mh_search_text (stream,msgno,d,n)
  1303.     MAILSTREAM *stream;
  1304.     long msgno;
  1305.     char *d;
  1306.     long n;
  1307. {
  1308.   char *t = mh_fetchheader (stream,msgno);
  1309.   return (t && search (t,strlen (t),d,n)) ||
  1310.     mh_search_body (stream,msgno,d,n);
  1311. }
  1312.  
  1313. char mh_search_bcc (stream,msgno,d,n)
  1314.     MAILSTREAM *stream;
  1315.     long msgno;
  1316.     char *d;
  1317.     long n;
  1318. {
  1319.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1320.                 /* get text for address */
  1321.   rfc822_write_address (LOCAL->buf,mh_fetchstructure (stream,msgno,NIL)->bcc);
  1322.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1323. }
  1324.  
  1325.  
  1326. char mh_search_cc (stream,msgno,d,n)
  1327.     MAILSTREAM *stream;
  1328.     long msgno;
  1329.     char *d;
  1330.     long n;
  1331. {
  1332.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1333.                 /* get text for address */
  1334.   rfc822_write_address (LOCAL->buf,mh_fetchstructure (stream,msgno,NIL)->cc);
  1335.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1336. }
  1337.  
  1338.  
  1339. char mh_search_from (stream,msgno,d,n)
  1340.     MAILSTREAM *stream;
  1341.     long msgno;
  1342.     char *d;
  1343.     long n;
  1344. {
  1345.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1346.                 /* get text for address */
  1347.   rfc822_write_address (LOCAL->buf,mh_fetchstructure (stream,msgno,NIL)->from);
  1348.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1349. }
  1350.  
  1351.  
  1352. char mh_search_to (stream,msgno,d,n)
  1353.     MAILSTREAM *stream;
  1354.     long msgno;
  1355.     char *d;
  1356.     long n;
  1357. {
  1358.   LOCAL->buf[0] = '\0';        /* initially empty string */
  1359.                 /* get text for address */
  1360.   rfc822_write_address (LOCAL->buf,mh_fetchstructure (stream,msgno,NIL)->to);
  1361.   return search (LOCAL->buf,strlen (LOCAL->buf),d,n);
  1362. }
  1363.  
  1364. /* Search parsers */
  1365.  
  1366.  
  1367. /* Parse a date
  1368.  * Accepts: function to return
  1369.  *        pointer to date integer to return
  1370.  * Returns: function to return
  1371.  */
  1372.  
  1373. search_t mh_search_date (f,n)
  1374.     search_t f;
  1375.     long *n;
  1376. {
  1377.   long i;
  1378.   char *s;
  1379.   MESSAGECACHE elt;
  1380.                 /* parse the date and return fn if OK */
  1381.   return (mh_search_string (f,&s,&i) && mail_parse_date (&elt,s) &&
  1382.       (*n = (elt.year << 9) + (elt.month << 5) + elt.day)) ? f : NIL;
  1383. }
  1384.  
  1385. /* Parse a flag
  1386.  * Accepts: function to return
  1387.  *        pointer to string to return
  1388.  * Returns: function to return
  1389.  */
  1390.  
  1391. search_t mh_search_flag (f,d)
  1392.     search_t f;
  1393.     char **d;
  1394. {
  1395.                 /* get a keyword, return if OK */
  1396.   return (*d = strtok (NIL," ")) ? f : NIL;
  1397. }
  1398.  
  1399.  
  1400. /* Parse a string
  1401.  * Accepts: function to return
  1402.  *        pointer to string to return
  1403.  *        pointer to string length to return
  1404.  * Returns: function to return
  1405.  */
  1406.  
  1407. search_t mh_search_string (f,d,n)
  1408.     search_t f;
  1409.     char **d;
  1410.     long *n;
  1411. {
  1412.   char *c = strtok (NIL,"");    /* remainder of criteria */
  1413.   if (c) {            /* better be an argument */
  1414.     switch (*c) {        /* see what the argument is */
  1415.     case '\0':            /* catch bogons */
  1416.     case ' ':
  1417.       return NIL;
  1418.     case '"':            /* quoted string */
  1419.       if (!(strchr (c+1,'"') && (*d = strtok (c,"\"")) && (*n = strlen (*d))))
  1420.     return NIL;
  1421.       break;
  1422.     case '{':            /* literal string */
  1423.       *n = strtol (c+1,&c,10);    /* get its length */
  1424.       if (*c++ != '}' || *c++ != '\015' || *c++ != '\012' ||
  1425.       *n > strlen (*d = c)) return NIL;
  1426.       c[*n] = '\255';        /* write new delimiter */
  1427.       strtok (c,"\255");    /* reset the strtok mechanism */
  1428.       break;
  1429.     default:            /* atomic string */
  1430.       *n = strlen (*d = strtok (c," "));
  1431.       break;
  1432.     }
  1433.     return f;
  1434.   }
  1435.   else return NIL;
  1436. }
  1437.